package cn.bqjr.eily.shiro.spring.boot.autoconfigure;

import cn.bqjr.eily.shiro.spring.boot.*;
import cn.bqjr.eily.shiro.spring.boot.entity.ShiroProperties;
import cn.bqjr.eily.shiro.spring.boot.entity.ShiroUrlFilter;
import cn.bqjr.eily.shiro.spring.boot.filter.AccessTokenFilter;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;

import javax.servlet.Filter;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Shiro相关bean注册
 */
@Configuration
@Import({ShiroProperties.class,
        RedisShiroSessionDAO.class,
        ShiroRedisCacheManager.class,
        UserPwdCredentialsMatcher.class,
        RedisLoginRetryMgr.class})
public class ShiroAutoconfigure {

    /**
     * shiro过滤器注册
     *
     * @param securityManager
     * @param urlFilterService
     * @param filterRegister
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(ShiroFilterFactoryBean.class)
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager,
                                              IShiroUrlFilterService urlFilterService,
                                              ShiroProperties properties,
                                              @Autowired(required = false) IShiroFilterRegister filterRegister) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 默认自定义过滤器
        Map<String, Filter> filterMap = shiroFilterFactoryBean.getFilters();
        filterMap.put("hasToken", new AccessTokenFilter(properties.getBase().getTokenHeaderName()));

        // 用户自定义过滤器
        if (null != filterRegister) {
            filterRegister.register(filterMap);
        }
        shiroFilterFactoryBean.setFilters(filterMap);

        // URL过滤
        Map<String, String> filterChainDefinitionMap;
        List<ShiroUrlFilter> urlFilterList = urlFilterService.selectAll();
        filterChainDefinitionMap = urlFilterList.stream()
                .collect(Collectors.toMap(
                        ShiroUrlFilter::getFilterUrl,
                        ShiroUrlFilter::getFilterList,
                        (e, n) -> n,
                        LinkedHashMap::new));
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * Cookie配置，仅当hiro.sessionManager.sessionIdCookieEnabled为True时启用
     *
     * @return
     */
    @Bean
    @ConditionalOnExpression("${shiro.sessionManager.sessionIdCookieEnabled:false}")
    @ConfigurationProperties(prefix = "shiro.cookie.sessionIdCookie")
    public Cookie sessionIdCookie() {
        return new SimpleCookie();
    }

    /**
     * 自定义Realm-账号密码
     *
     * @return
     */
    @Bean(name = "userPwdRealm")
    public AuthorizingRealm userPwdRealm(UserPwdCredentialsMatcher userPwdMacther) {
        AuthorizingRealm userPwdRealm = new UserPwdRealm();
        userPwdRealm.setCredentialsMatcher(userPwdMacther);
        return userPwdRealm;
    }

    @Bean(name = "sessionManager")
    @ConditionalOnMissingBean(SessionManager.class)
    @ConfigurationProperties(prefix = "shiro.sessionManager")
    public DefaultWebSessionManager sessionManager(
            RedisShiroSessionDAO sessionDAO,
            ShiroRedisCacheManager redisCacheManager,
            @Autowired(required = false) Collection<SessionListener> listeners,
            ShiroProperties shiroProperties) {
        ShiroSessionManager sessionManager = new ShiroSessionManager();
        sessionManager.setSessionDAO(sessionDAO);
        sessionManager.setCacheManager(redisCacheManager);
        sessionManager.setSessionIdName(shiroProperties.getBase().getTokenHeaderName());
        if (shiroProperties.getSessionManager().isSessionIdCookieEnabled()) {
            sessionManager.setSessionIdCookie(sessionIdCookie());
        }
        sessionManager.setSessionListeners(listeners);
        return sessionManager;
    }

    @Bean
    @ConditionalOnMissingBean(SecurityManager.class)
    @DependsOn({"userPwdRealm"})
    public SecurityManager securityManager(SessionManager sessionManager,
                                           UserPwdRealm userPwdRealm,
                                           ShiroRedisCacheManager redisCacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setCacheManager(redisCacheManager);
        securityManager.setSessionManager(sessionManager);
        securityManager.setRealm(userPwdRealm);
        return securityManager;
    }

    /**
     * 在方法中 注入 securityManager,进行代理控制
     */
    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager) {
        MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();
        bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        bean.setArguments(new Object[]{securityManager});
        return bean;
    }

    /**
     * Shiro生命周期处理器
     *
     * @return
     */
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * AOP式方法级权限检查
     *
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}
